package com.snake;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;
import android.view.View;

import androidx.annotation.NonNull;

import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

// 游戏
class Game implements Callback {
    // 定时器任务
    private static class GameTimerTask extends TimerTask {
        private final Handler mCallback;    // 回调
        
        /**
         * 构造方法。
         * @param callback  回调
         */
        public GameTimerTask(Callback callback) {
            mCallback = new Handler(callback);
        }

        /**
         * 定时器事件的响应方法。
         */
        @Override
        public void run() {
            mCallback.sendEmptyMessage(Const.UM_TIMER);
        }
    }

    // 状态
    public enum Status {
        END,    // 已结束
        PAUSED, // 暂停中
        PLAYING // 游戏中
    }

    private SnakeBody mApple;               // 果子
    private final Handler mCallback;        // 回调
    private int mDifficulty;                // 难度
    private final Canvas mDrawer;           // 绘图者
    private boolean mEnableMusic;           // 音乐开关
    private final View mGameBoard;          // 游戏区域
    private final MediaPlayer mMusicPlayer; // 音乐播放者
    private final Paint mPaint;             // 绘图相关信息
    private final Random mRandom;           // 随机数生成器
    private int mScore;                     // 得分
    private Snake mSnake;                   // 蛇
    private Status mStatus;                 // 状态
    private final Timer mTimer;             // 定时器
    private TimerTask mTimerTask;           // 定时器任务

    /**
     * 构造方法。
     * @param callback  回调
     * @param drawer    绘图者
     * @param gameBoard 游戏区域
     */
    public Game(Callback callback, Canvas drawer, View gameBoard) {
        // 初始化成员
        mCallback = new Handler(callback);
        mDifficulty = 1;
        mDrawer = drawer;
        mEnableMusic = false;
        mGameBoard = gameBoard;
        mMusicPlayer = MediaPlayer.create(gameBoard.getContext(), R.raw.music);
        mMusicPlayer.setLooping(true);
        mPaint = new Paint();
        mRandom = new Random();
        mScore = 0;
        mStatus = Status.END;
        mTimer = new Timer();

        // 重绘游戏区域
        this.drawGameBoard();
        mGameBoard.invalidate();
    }

    /**
     * 在随机位置创建果子。
     */
    private void createApple() {
        final Point point = new Point();
        boolean used;

        // 创建果子
        do {
            // 取 x、y 的随机值
            point.x = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_H - 2) + 1;
            point.y = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_V - 2) + 1;
            used = this.pointIsUsed(point);
        } while (used);
        mApple = new SnakeBody(mDrawer, point, SnakeBody.Type.APPLE);
    }

    /**
     * 画游戏区域。
     */
    private void drawGameBoard() {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Const.GAME_BOARD_COLOR);
        mDrawer.drawRect(0, 0, mGameBoard.getWidth(), mGameBoard.getHeight(), mPaint);
    }

    /**
     * 游戏结束。
     * @param isWon 胜利则为 true，否则为 false
     */
    private void gameOver(boolean isWon) {
        // 将状态设置为已结束
        mStatus = Status.END;

        // 停止定时器
        mTimerTask.cancel();

        // 停止播放音乐
        if (mEnableMusic) {
            try {
                mMusicPlayer.stop();
                mMusicPlayer.prepare();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 发送通知
        final Message message = Message.obtain();
        message.what = Const.UM_GAME_OVER;
        message.obj = isWon;
        mCallback.sendMessage(message);
    }

    /**
     * 获取游戏状态。
     * @return 游戏状态
     */
    public Status getStatus() {
        return mStatus;
    }
    
    /**
     * 自定义事件的响应方法。
     * @param message   事件
     * @return 事件在此被处理则返回 true，否则返回 false
     */
    @Override
    public boolean handleMessage(@NonNull Message message) {
        this.onTimer();
        return true;
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private boolean isWon() {
        return mScore >= Const.WIN_NUMBER;
    }

    /**
     * 滑动手势的响应方法。
     * @param direction 方向
     * @return 事件在此被处理则返回 true，否则返回 false
     */
    public boolean onFling(int direction) {
        if (mStatus != Status.PLAYING) {
            return false;
        }
        mSnake.turn(direction);
        return true;
    }

    /**
     * 定时器事件的响应方法。
     */
    public void onTimer() {
        // 移动蛇
        final boolean isDead = mSnake.move();
        if (isDead) {
            this.gameOver(false);
            return;
        }

        // 检查是否碰到果子
        Message message;
        final SnakeBody head = mSnake.getHead();
        if (head.getPosition().equals(mApple.getPosition())) {
            // 碰到果子，将果子加入蛇链表
            mSnake.eat(mApple);
            this.createApple();
            mScore++;
            message = Message.obtain();
            message.what = Const.UM_SCORE;
            message.arg1 = mScore;
            mCallback.sendMessage(message);

            // 判断是否胜利
            if (this.isWon()) {
                this.gameOver(true);
                return;
            }

            // 判断是否达到增加难度的条件
            if (mScore % Const.DIFF_BASE == 0 && mDifficulty != Const.DIFF_MAX) {
                mDifficulty++;
                message = Message.obtain();
                message.what = Const.UM_DIFFICULTY_UP;
                message.arg1 = mDifficulty;
                mCallback.sendMessage(message);
                mTimerTask.cancel();
                final long elapse = Const.TIMER_INIT - Const.TIMER_GROW * (mDifficulty - 1);
                mTimerTask = new GameTimerTask(this);
                mTimer.schedule(mTimerTask, elapse, elapse);
            }
        }

        // 通知游戏区域重绘
        mGameBoard.invalidate();
    }

    /**
     * 游戏暂停。
     */
    public void pause() {
        if (mStatus == Status.PLAYING) {
            // 停止定时器
            mTimerTask.cancel();

            // 停止播放音乐
            if (mEnableMusic) {
                mMusicPlayer.pause();
            }

            // 将状态设置为已暂停
            mStatus = Status.PAUSED;
        } else if (mStatus == Status.PAUSED) {
            // 开启定时器
            mTimerTask = new GameTimerTask(this);
            final long elapse = Const.TIMER_INIT - Const.TIMER_GROW * (mDifficulty - 1);
            mTimer.schedule(mTimerTask, elapse, elapse);

            // 播放音乐
            if (mEnableMusic) {
                mMusicPlayer.start();
            }

            // 将状态设置为游戏中
            mStatus = Status.PLAYING;
        }
    }

    /**
     * 播放音乐。
     * @return 正在播放音乐则为 true，否则为 false
     */
    public boolean playMusic() {
        mEnableMusic = !mEnableMusic;

        if (mEnableMusic) {
            if (mStatus == Status.PLAYING) {
                mMusicPlayer.start();
            }
        } else {
            mMusicPlayer.pause();
        }

        return mEnableMusic;
    }

    /**
     * 判断某个点是否被占用。
     * @param point 点
     * @return 被占用了则返回 true，否则返回 false
     */
    private boolean pointIsUsed(Point point) {
        // 检查点是否在蛇上
        if (mSnake != null) {
            if (mSnake.pointIsOnMe(point)) {
                return true;
            }
        }

        // 检查点是否在果子上
        if (mApple != null) {
            return mApple.getPosition().equals(point);
        }

        return false;
    }

    /**
     * 游戏开始。
     */
    public void start() {
        // 重绘游戏区域
        this.drawGameBoard();

        // 创建蛇
        mSnake = new Snake(mDrawer);

        // 创建果子
        this.createApple();

        // 通知游戏区域重绘
        mGameBoard.invalidate();

        // 重置难度
        mDifficulty = 1;

        // 重置分数
        mScore = 0;

        // 开启定时器
        if (mTimerTask != null && mStatus != Status.PAUSED || mStatus != Status.END) {
            mTimerTask.cancel();
        }
        mTimerTask = new GameTimerTask(this);
        mTimer.schedule(mTimerTask, Const.TIMER_INIT, Const.TIMER_INIT);

        // 播放音乐
        if (mEnableMusic) {
            mMusicPlayer.start();
        }

        // 将状态设置为游戏中
        mStatus = Status.PLAYING;
    }
}
